package org.xmdl.silk.interpreter;

import org.xmdl.silk.Expression;
import org.xmdl.silk.Literal;
import org.xmdl.silk.NumDouble;
import org.xmdl.silk.NumInteger;
import org.xmdl.silk.Operation;
import org.xmdl.silk.ValueItem;
import org.xmdl.silk.Variable;
import org.xmdl.silk.VariableRef;
import org.xmdl.silk.visit.VisitorAdapter;
import org.xmdl.silk.wrap.ExpressionWrap;
import org.xmdl.silk.wrap.Wrapped;

public class Evaluator extends VisitorAdapter {

	private Value value;
	
	protected ExecutionFrame frame;

	public Evaluator(ExecutionFrame frame) {
		super();
		this.frame = frame;
	}

	public Value value() {
		return value;
	}

	@Override
	public boolean visit(NumDouble host) {
		value = new NumberValue(host.getValue());
		return true;
	}

	@Override
	public boolean visit(NumInteger host) {
		value = new NumberValue(host.getValue());
		return true;
	}

	@Override
	public boolean visit(Literal host) {
		value = new LiteralValue(host.getValue());
		return true;
	}

	@Override
	public boolean visitLeave(VariableRef host) {
		Variable var = host.getVariable();
		Value val = frame.getValue(var);
		if (val instanceof Value) {
			value = (Value) val;
		}
		return value != null;
	}

	@Override
	public boolean visitLeave(Operation host) {
		Expression left = host.getLeft();
		Value l = eval(left);
		Expression right = host.getRight();
		String op = host.getOperator();
		if (right == null) {
			value = l;
		} else {
			Expression rr = null;
			Expression rl = null;
			String rop = null;
			if (right instanceof Operation) {
				Operation ro = (Operation) right;
				rr = ro.getRight();
				rl = ro.getLeft();
				rop = ro.getOperator();
			}
			
			if (rr != null) {
				//traverse
				Value rlv = eval(rl);
				Value rrv = eval(rr);
				value = op(rop, op(op, l, rlv), rrv);
			} else {
				//no traverse
				Value r = eval(right);
				value = op(op, l, r);
			}
		}
		return super.visitLeave(host);
	}

	private Value op(String op, Value l, Value r) {
		if (!(l instanceof NumberValue))
			throw new NotANumberException(l);
			
		if (!(r instanceof NumberValue))
			throw new NotANumberException(r);
			
		NumberValue lnv = (NumberValue) l;
		NumberValue rnv = (NumberValue) r;
		Value v = null;
		Operator operator = Operator.get(op);
		switch (operator) {
		case SUB:
			v = new NumberValue(lnv.doubleValue() - rnv.doubleValue());
			break;
		case SUM:
			v = new NumberValue(lnv.doubleValue() + rnv.doubleValue());
			break;
		case DIV:
			v = new NumberValue(lnv.doubleValue() / rnv.doubleValue());
			break;
		case MUL:
			v = new NumberValue(lnv.doubleValue() * rnv.doubleValue());
			break;
		default:
			break;
		}
		return v;
	}

	private Value eval(Expression e) {
		Evaluator eval = new Evaluator(frame);
		((ExpressionWrap)e).accept(eval);
		return eval.value();
	}

	public Value evaluate(ValueItem exp) {
		Wrapped w = (Wrapped) exp;
		w.accept(this);
		return value();
	}

}
